
import { OnInit, OnDestory } from '@core/types'
import { RedisClientType, createClient } from 'redis';
import { Logger } from 'pino'
import { createLogger } from '@nodejs/logger';
export interface IEncoder<T> {
    (val: T): string;
}
export const defaultEncode: IEncoder<any> = (val: any) => {
    return JSON.stringify(val)
}
export interface IDecoder<T> {
    (val: string): T;
}
export const defaultDecode: IDecoder<any> = (val: any) => {
    return JSON.parse(val)
}
export class RedisHashMap<T = any> implements OnInit, OnDestory {
    redis: RedisClientType | null;
    logger: Logger;
    isDestoried: boolean = false;
    constructor(
        public key: string,
        public database: number = 0,
        public encode: IEncoder<T>,
        public decode: IDecoder<T>
    ) { }
    static async useRedisHashMap<T>(
        key: string,
        database: number = 0,
        encoder: IEncoder<T> = defaultEncode,
        decoder: IDecoder<T> = defaultDecode
    ): Promise<RedisHashMap<T>> {
        const redisHashMap = new RedisHashMap<T>(key, database, encoder, decoder)
        await redisHashMap.onInit();
        return redisHashMap;
    }
    async getRedis(): Promise<RedisClientType> {
        if (this.isDestoried) throw new Error(`RedisHashMap.${this.key} has destoried`)
        if (this.redis) return this.redis;
        return await this.onInit()
    }

    async onInit(): Promise<RedisClientType> {
        this.logger = await createLogger(`RedisHashMap.${this.key}`)
        const redisUrl = process.env.REDIS_URL || ``;
        this.redis = createClient({
            url: redisUrl,
            database: this.database
        }) as RedisClientType;
        await this.redis.connect()
        this.redis.on('error', async (error) => {
            this.logger.error(error)
            this.redis = null;
        })
        return this.redis;
    }
    async onDestory(): Promise<void> {
        this.isDestoried = true;
        if (this.redis) {
            await this.redis.disconnect().catch(e => this.logger.error(e))
        }
        this.redis = null;
    }
    async clear(): Promise<number> {
        const redis = await this.getRedis()
        return await redis.del(this.key)
    }
    async delete(key: string): Promise<number> {
        const redis = await this.getRedis()
        return await redis.hDel(this.key, key)
    }
    async forEach(callbackfn: (value: T, key: string, map: Map<string, T>) => void): Promise<void> {
        const map = await this.entries();
        map.forEach(callbackfn)
    }
    async get(key: string): Promise<T | undefined> {
        const redis = await this.getRedis()
        const val = await redis.hGet(this.key, key)
        if (val) return this.decode(val)
    }
    async has(key: string): Promise<boolean> {
        const redis = await this.getRedis()
        return await redis.hExists(this.key, key)
    }
    async set(key: string, value: T): Promise<void> {
        const redis = await this.getRedis()
        await redis.hSet(this.key, key, this.encode(value))
    }
    async size(): Promise<number> {
        const redis = await this.getRedis()
        return redis.hLen(this.key)
    }
    async entries(): Promise<Map<string, T>> {
        const redis = await this.getRedis()
        const result = await redis.hGetAll(this.key)
        let map: Map<string, T> = new Map()
        Object.keys(result).map(key => {
            const val = this.decode(result[key])
            map.set(key, val)
        })
        return map;
    }
    async keys(): Promise<string[]> {
        const redis = await this.getRedis()
        return await redis.hKeys(this.key)
    }
    async values(): Promise<T[]> {
        const redis = await this.getRedis()
        const result = await redis.hVals(this.key)
        return result.map(v => this.decode(v))
    }
}